Enter the directory of the maca folder on your drive and the name of the tissue you want to analyze.
tissue_of_interest = "Liver"
Load the requisite packages and some additional helper functions.
library(here)
library(useful)
library(Seurat)
library(dplyr)
library(Matrix)
library(ontologyIndex)
cell_ontology = get_ontology('https://raw.githubusercontent.com/obophenotype/cell-ontology/master/cl-basic.obo', extract_tags='everything')
validate_cell_ontology = function(cell_ontology_class){
in_cell_ontology = sapply(cell_ontology_class, function(x) is.element(x, cell_ontology$name) || is.na(x))
if (!all(in_cell_ontology)) {
message = paste0('"', cell_ontology_class[!in_cell_ontology], '" is not in the cell ontology
')
stop(message)
}
}
convert_to_cell_ontology_id = function(cell_ontology_class){
return(sapply(cell_ontology_class, function(x) as.vector(cell_ontology$id[cell_ontology$name == x])[1]))
}
save_dir = here('00_data_ingest', '04_tissue_robj_generated')
# read the metadata to get the plates we want
droplet_metadata_filename = here('00_data_ingest', '01_droplet_raw_data', 'metadata_droplet.csv')
droplet_metadata <- read.csv(droplet_metadata_filename, sep=",", header = TRUE)
colnames(droplet_metadata)[1] <- "channel"
droplet_metadata
Subset the metadata on the tissue.
tissue_metadata = filter(droplet_metadata, tissue == tissue_of_interest)[,c('channel','tissue','subtissue','mouse.sex')]
tissue_metadata
Use only the metadata rows corresponding to Bladder plates. Make a plate barcode dataframe to “expand” the per-plate metadata to be per-cell.
# Load the gene names and set the metadata columns by opening the first file
subfolder = paste0(tissue_of_interest, '-', tissue_metadata$channel[1])
raw.data <- Read10X(data.dir = here('00_data_ingest', '01_droplet_raw_data', 'droplet', subfolder))
colnames(raw.data) <- lapply(colnames(raw.data), function(x) paste0(tissue_metadata$channel[1], '_', x))
meta.data = data.frame(row.names = colnames(raw.data))
meta.data['channel'] = tissue_metadata$channel[1]
if (length(tissue_metadata$channel) > 1){
# Some tissues, like Thymus and Heart had only one channel
for(i in 2:nrow(tissue_metadata)){
subfolder = paste0(tissue_of_interest, '-', tissue_metadata$channel[i])
new.data <- Read10X(data.dir = here('00_data_ingest', '01_droplet_raw_data', 'droplet', subfolder))
colnames(new.data) <- lapply(colnames(new.data), function(x) paste0(tissue_metadata$channel[i], '_', x))
new.metadata = data.frame(row.names = colnames(new.data))
new.metadata['channel'] = tissue_metadata$channel[i]
raw.data = cbind(raw.data, new.data)
meta.data = rbind(meta.data, new.metadata)
}
}
rnames = row.names(meta.data)
meta.data <- merge(meta.data, tissue_metadata, sort = F)
row.names(meta.data) <- rnames
dim(raw.data)
[1] 23433 1924
corner(raw.data)
[1] 0 0 0 0 2
head(meta.data)
Order the cells alphabetically to ensure consistency.
ordered_cell_names = order(colnames(raw.data))
raw.data = raw.data[,ordered_cell_names]
meta.data = meta.data[ordered_cell_names,]
corner(raw.data)
[1] 0 0 0 0 2
head(meta.data)
Process the raw data and load it into the Seurat object.
# Find ERCC's, compute the percent ERCC, and drop them from the raw data.
erccs <- grep(pattern = "^ERCC-", x = rownames(x = raw.data), value = TRUE)
percent.ercc <- Matrix::colSums(raw.data[erccs, ])/Matrix::colSums(raw.data)
ercc.index <- grep(pattern = "^ERCC-", x = rownames(x = raw.data), value = FALSE)
raw.data <- raw.data[-ercc.index,]
# Create the Seurat object with all the data
tiss <- CreateSeuratObject(raw.data = raw.data, project = tissue_of_interest,
min.cells = 1, min.genes = 0)
# Continue from here onwards !
tiss <- AddMetaData(object = tiss, meta.data)
tiss <- AddMetaData(object = tiss, percent.ercc, col.name = "percent.ercc")
# Change default name for sums of counts from nUMI to nReads
# colnames(tiss@meta.data)[colnames(tiss@meta.data) == 'nUMI'] <- 'nReads'
# Create metadata columns for cell_ontology_classs and subcell_ontology_classs
tiss@meta.data[,'cell_ontology_class'] <- NA
tiss@meta.data[,'subcell_ontology_class'] <- NA
Calculate percent ribosomal genes.
ribo.genes <- grep(pattern = "^Rp[sl][[:digit:]]", x = rownames(x = tiss@data), value = TRUE)
percent.ribo <- Matrix::colSums(tiss@raw.data[ribo.genes, ])/Matrix::colSums(tiss@raw.data)
tiss <- AddMetaData(object = tiss, metadata = percent.ribo, col.name = "percent.ribo")
A sanity check: genes per cell vs reads per cell.
GenePlot(object = tiss, gene1 = "nUMI", gene2 = "nGene", use.raw=T)

Filter out cells with few reads and few genes.
tiss <- FilterCells(object = tiss, subset.names = c("nGene", "nUMI"), low.thresholds = c(500, 1000))
Normalize the data, then center and scale.
tiss <- NormalizeData(object = tiss, scale.factor = 1e4)
Performing log-normalization
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
tiss <- ScaleData(object = tiss)
[1] "Scaling data matrix"
|
| | 0%
|
|===============================================================================================================| 100%
tiss <- FindVariableGenes(object = tiss, do.plot = TRUE, x.high.cutoff = Inf, y.cutoff = 0.5)
Calculating gene means
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating gene variance to mean ratios
0% 10 20 30 40 50 60 70 80 90 100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|

Run Principal Component Analysis.
tiss <- RunPCA(object = tiss, do.print = FALSE)
tiss <- ProjectPCA(object = tiss, do.print = FALSE)

Later on (in FindClusters and TSNE) you will pick a number of principal components to use. This has the effect of keeping the major directions of variation in the data and, ideally, supressing noise. There is no correct answer to the number to use, but a decent rule of thumb is to go until the plot plateaus.
PCElbowPlot(object = tiss)

Choose the number of principal components to use.
# Set number of principal components.
n.pcs = 10
The clustering is performed based on a nearest neighbors graph. Cells that have similar expression will be joined together. The Louvain algorithm looks for groups of cells with high modularity–more connections within the group than between groups. The resolution parameter determines the scale. Higher resolution will give more clusters, lower resolution will give fewer.
For the top-level clustering, aim to under-cluster instead of over-cluster. It will be easy to subset groups and further analyze them below.
# Set resolution
res.used <- 3.5
tiss <- FindClusters(object = tiss, reduction.type = "pca", dims.use = 1:n.pcs,
resolution = res.used, print.output = 0, save.SNN = TRUE)
We use TSNE solely to visualize the data.
# If cells are too spread out, you can raise the perplexity. If you have few cells, try a lower perplexity (but never less than 10).
tiss <- RunTSNE(object = tiss, dims.use = 1:n.pcs, seed.use = 10, perplexity=30)
TSNEPlot(object = tiss, do.label = T, pt.size = 1.2, label.size = 4)

TSNEPlot(tiss, group.by="mouse.sex")

Significant genes:
hepatocyte: Alb, Ttr, Apoa1, and Serpina1c pericentral: Cyp2e1, Glul, Oat, Gulo midlobular: Ass1, Hamp, Gstp1, Ubb periportal: Cyp2f2, Pck1, Hal, Cdh1
endothelial cells: Pecam1, Nrp1, Kdr+ and Oit3+ Kuppfer cells: Emr1, Clec4f, Cd68, Irf7 NK/NKT cells: Zap70, Il2rb, Nkg7, Cxcr6, Klr1c, Gzma B cells: Cd79a, Cd79b, Cd74 and Cd19 Immune cells: Ptprc
genes_hep = c('Alb', 'Ttr', 'Apoa1', 'Serpina1c',
'Cyp2e1', 'Glul', 'Oat', 'Gulo',
'Ass1', 'Hamp', 'Gstp1', 'Ubb',
'Cyp2f2', 'Pck1', 'Hal', 'Cdh1')
genes_endo = c('Pecam1', 'Nrp1', 'Kdr','Oit3')
genes_kuppfer = c('Emr1', 'Clec4f', 'Cd68', 'Irf7')
genes_nk = c('Zap70', 'Il2rb', 'Nkg7', 'Cxcr6', 'Gzma')
genes_b = c('Cd79a', 'Cd79b', 'Cd74')
genes_bec = c('Epcam', 'Krt19', 'Krt7')
genes_immune = 'Ptprc'
all_genes = c(genes_hep, genes_endo, genes_kuppfer, genes_nk, genes_b, genes_bec, genes_immune)

Dotplots let you see the intensity of exppression and the fraction of cells expressing for each of your genes of interest. The radius shows you the percent of cells in that cluster with at least one read sequenced from that gene. The color level indicates the average Z-score of gene expression for cells in that cluster, where the scaling is done over taken over all cells in the sample.
We have various immune cell types in the last cluster




Using the markers above, we can confidentaly label many of the clusters:
19: endothelial cells 20: bile duct epithelial cells 21: immune cells rest are hepatocytes
We will add those cell_ontology_classs to the dataset.
tiss <- StashIdent(object = tiss, save.name = "cluster.ids")
cluster.ids <- c(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
free_cell_ontology_class <- c(
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"endothelial cells",
"bile duct epithelial cells",
"immune cells")
cell_ontology_class <- c(
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"hepatocyte",
"endothelial cell of hepatic sinusoid",
"duct epithelial cell",
"leukocyte")
validate_cell_ontology(cell_ontology_class)
cell_ontology_id = convert_to_cell_ontology_id(cell_ontology_class)
tiss@meta.data['free_cell_ontology_class'] <- as.character(plyr::mapvalues(x = tiss@ident, from = cluster.ids, to = free_cell_ontology_class))
validate_cell_ontology(cell_ontology_class)
cell_ontology_id = convert_to_cell_ontology_id(cell_ontology_class)
tiss@meta.data['free_annotation'] <- as.character(plyr::mapvalues(x = tiss@ident, from = cluster.ids, to = free_annotation))
tiss@meta.data['cell_ontology_id'] <- as.character(plyr::mapvalues(x = tiss@ident, from = cluster.ids, to = cell_ontology_id))
Checking for batch effects
Color by metadata, like plate barcode, to check for batch effects.
TSNEPlot(object = subtiss, do.return = TRUE, group.by = "channel")

Subcluster
subtiss = SubsetData(tiss, ident.use = c(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18))
VlnPlot(object = subtiss, features.plot = c("nGene", "nUMI"), nCol = 2)
subtiss <- subtiss %>% ScaleData() %>%
FindVariableGenes(do.plot = TRUE, x.high.cutoff = Inf, y.cutoff = 0.5) %>%
RunPCA(do.print = FALSE)
PCHeatmap(object = subtiss, pc.use = 1:3, cells.use = 20, do.balanced = TRUE, label.columns = FALSE, num.genes = 8)
PCElbowPlot(subtiss)
sub.n.pcs = 8
sub.res.use = .5
subtiss <- subtiss %>% FindClusters(reduction.type = "pca", dims.use = 1:sub.n.pcs,
resolution = sub.res.use, print.output = 0, save.SNN = TRUE) %>%
RunTSNE(dims.use = 1:sub.n.pcs, seed.use = 10, perplexity=8)
TSNEPlot(object = subtiss, do.label = T, pt.size = .5, label.size = 4)


BuildClusterTree(subtiss)

From these genes, it appears that the clusters represent:
0: midlobular male 1: pericentral female 2: periportal female 3: periportal male 4: midlobular male 5: pericentral male 6: midlobular female 7: midlobular female
The multitude of clusters of each type correspond mostly to individual animals/sexes.
table(FetchData(subtiss, c('mouse.id','ident')) %>% droplevels())
sub.cluster.ids <- c(0, 1, 2, 3, 4, 5, 6, 7)
sub.free_cell_ontology_class <- c("midlobular male", "pericentral female", "periportal female", "periportal male", "midlobular male", "pericentral male", "midlobular female", "midlobular female")
sub.cell_ontology_class <- c("hepatocyte", "hepatocyte", "hepatocyte", "hepatocyte", "hepatocyte", "hepatocyte", "hepatocyte", "hepatocyte")
validate_cell_ontology(sub.cell_ontology_class)
sub.cell_ontology_id = convert_to_cell_ontology_id(sub.cell_ontology_class)
subtiss@meta.data['free_cell_ontology_class'] <- as.character(plyr::mapvalues(x = subtiss@ident, from = sub.cluster.ids, to = sub.free_cell_ontology_class))
validate_cell_ontology(sub.cell_ontology_class)
sub.cell_ontology_id = convert_to_cell_ontology_id(sub.cell_ontology_class)
subtiss@meta.data['free_annotation'] <- as.character(plyr::mapvalues(x = subtiss@ident, from = sub.cluster.ids, to = sub.free_annotation))
Error in plyr::mapvalues(x = subtiss@ident, from = sub.cluster.ids, to = sub.free_annotation) :
`from` and `to` vectors are not the same length.
Add subcluster cell_ontology_classs to main cell_ontology_class
sub.cells = rownames(subtiss@meta.data)
tiss@meta.data[sub.cells, 'free_cell_ontology_class'] = subtiss@meta.data[,'free_cell_ontology_class']
tiss@meta.data[sub.cells, 'cell_ontology_class'] = subtiss@meta.data[,'cell_ontology_class']
tiss@meta.data[sub.cells, 'cell_ontology_id'] = subtiss@meta.data[,'cell_ontology_id']
Checking for batch effects
Color by metadata, like plate barcode, to check for batch effects.
TSNEPlot(object = subtiss, do.return = TRUE, group.by = "mouse.sex")
Final coloring
Color by cell ontology class on the original TSNE.
TSNEPlot(object = tiss, do.return = TRUE, group.by = "cell_ontology_class")
Error in seq.default(h[1], h[2], length.out = n) :
'to' must be a finite number

Save the Robject for later
filename = here('00_data_ingest', '04_tissue_robj_generated',
paste0("droplet", tissue_of_interest, "_seurat_subtiss_.5res.Robj"))
print(filename)
[1] "/Users/rasika.p/Documents/GitHub/tabula-muris/00_data_ingest/04_tissue_robj_generated/dropletLiver_seurat_subtiss_.5res.Robj"
save(subtiss, file=filename)
# To reload a saved object
filename = here('00_data_ingest', '04_tissue_robj_generated',
paste0("droplet", tissue_of_interest, "_seurat_tiss.Robj"))
load(file=filename)
LS0tCiB0aXRsZTogIkxpdmVyIERyb3BsZXQgTm90ZWJvb2siCiBvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpFbnRlciB0aGUgZGlyZWN0b3J5IG9mIHRoZSBtYWNhIGZvbGRlciBvbiB5b3VyIGRyaXZlIGFuZCB0aGUgbmFtZSBvZiB0aGUgdGlzc3VlIHlvdSB3YW50IHRvIGFuYWx5emUuCgpgYGB7cn0KdGlzc3VlX29mX2ludGVyZXN0ID0gIkxpdmVyIgpgYGAKCkxvYWQgdGhlIHJlcXVpc2l0ZSBwYWNrYWdlcyBhbmQgc29tZSBhZGRpdGlvbmFsIGhlbHBlciBmdW5jdGlvbnMuCgpgYGB7cn0KbGlicmFyeShoZXJlKQpsaWJyYXJ5KHVzZWZ1bCkKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KG9udG9sb2d5SW5kZXgpCmNlbGxfb250b2xvZ3kgPSBnZXRfb250b2xvZ3koJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9vYm9waGVub3R5cGUvY2VsbC1vbnRvbG9neS9tYXN0ZXIvY2wtYmFzaWMub2JvJywgZXh0cmFjdF90YWdzPSdldmVyeXRoaW5nJykKCnZhbGlkYXRlX2NlbGxfb250b2xvZ3kgPSBmdW5jdGlvbihjZWxsX29udG9sb2d5X2NsYXNzKXsKICBpbl9jZWxsX29udG9sb2d5ID0gc2FwcGx5KGNlbGxfb250b2xvZ3lfY2xhc3MsIGZ1bmN0aW9uKHgpIGlzLmVsZW1lbnQoeCwgY2VsbF9vbnRvbG9neSRuYW1lKSB8fCBpcy5uYSh4KSkKICBpZiAoIWFsbChpbl9jZWxsX29udG9sb2d5KSkgewogICAgbWVzc2FnZSA9IHBhc3RlMCgnIicsIGNlbGxfb250b2xvZ3lfY2xhc3NbIWluX2NlbGxfb250b2xvZ3ldLCAnIiBpcyBub3QgaW4gdGhlIGNlbGwgb250b2xvZ3kKJykKICAgIHN0b3AobWVzc2FnZSkKICB9Cn0KY29udmVydF90b19jZWxsX29udG9sb2d5X2lkID0gZnVuY3Rpb24oY2VsbF9vbnRvbG9neV9jbGFzcyl7CiAgcmV0dXJuKHNhcHBseShjZWxsX29udG9sb2d5X2NsYXNzLCBmdW5jdGlvbih4KSBhcy52ZWN0b3IoY2VsbF9vbnRvbG9neSRpZFtjZWxsX29udG9sb2d5JG5hbWUgPT0geF0pWzFdKSkKfQpzYXZlX2RpciA9IGhlcmUoJzAwX2RhdGFfaW5nZXN0JywgJzA0X3Rpc3N1ZV9yb2JqX2dlbmVyYXRlZCcpCmBgYAoKCgpgYGB7cn0KIyByZWFkIHRoZSBtZXRhZGF0YSB0byBnZXQgdGhlIHBsYXRlcyB3ZSB3YW50CmRyb3BsZXRfbWV0YWRhdGFfZmlsZW5hbWUgPSBoZXJlKCcwMF9kYXRhX2luZ2VzdCcsICcwMV9kcm9wbGV0X3Jhd19kYXRhJywgJ21ldGFkYXRhX2Ryb3BsZXQuY3N2JykKCmRyb3BsZXRfbWV0YWRhdGEgPC0gcmVhZC5jc3YoZHJvcGxldF9tZXRhZGF0YV9maWxlbmFtZSwgc2VwPSIsIiwgaGVhZGVyID0gVFJVRSkKY29sbmFtZXMoZHJvcGxldF9tZXRhZGF0YSlbMV0gPC0gImNoYW5uZWwiCmRyb3BsZXRfbWV0YWRhdGEKYGBgCgpTdWJzZXQgdGhlIG1ldGFkYXRhIG9uIHRoZSB0aXNzdWUuCgpgYGB7cn0KdGlzc3VlX21ldGFkYXRhID0gZmlsdGVyKGRyb3BsZXRfbWV0YWRhdGEsIHRpc3N1ZSA9PSB0aXNzdWVfb2ZfaW50ZXJlc3QpWyxjKCdjaGFubmVsJywndGlzc3VlJywnc3VidGlzc3VlJywnbW91c2Uuc2V4JyldCnRpc3N1ZV9tZXRhZGF0YQpgYGAKCgpVc2Ugb25seSB0aGUgbWV0YWRhdGEgcm93cyBjb3JyZXNwb25kaW5nIHRvIEJsYWRkZXIgcGxhdGVzLiBNYWtlIGEgcGxhdGUgYmFyY29kZSBkYXRhZnJhbWUgdG8gImV4cGFuZCIgdGhlIHBlci1wbGF0ZSBtZXRhZGF0YSB0byBiZSBwZXItY2VsbC4KCmBgYHtyfQojIExvYWQgdGhlIGdlbmUgbmFtZXMgYW5kIHNldCB0aGUgbWV0YWRhdGEgY29sdW1ucyBieSBvcGVuaW5nIHRoZSBmaXJzdCBmaWxlCgpzdWJmb2xkZXIgPSBwYXN0ZTAodGlzc3VlX29mX2ludGVyZXN0LCAnLScsIHRpc3N1ZV9tZXRhZGF0YSRjaGFubmVsWzFdKQpyYXcuZGF0YSA8LSBSZWFkMTBYKGRhdGEuZGlyID0gaGVyZSgnMDBfZGF0YV9pbmdlc3QnLCAnMDFfZHJvcGxldF9yYXdfZGF0YScsICdkcm9wbGV0Jywgc3ViZm9sZGVyKSkKY29sbmFtZXMocmF3LmRhdGEpIDwtIGxhcHBseShjb2xuYW1lcyhyYXcuZGF0YSksIGZ1bmN0aW9uKHgpIHBhc3RlMCh0aXNzdWVfbWV0YWRhdGEkY2hhbm5lbFsxXSwgJ18nLCB4KSkKbWV0YS5kYXRhID0gZGF0YS5mcmFtZShyb3cubmFtZXMgPSBjb2xuYW1lcyhyYXcuZGF0YSkpCm1ldGEuZGF0YVsnY2hhbm5lbCddID0gdGlzc3VlX21ldGFkYXRhJGNoYW5uZWxbMV0KCmlmIChsZW5ndGgodGlzc3VlX21ldGFkYXRhJGNoYW5uZWwpID4gMSl7CiAgIyBTb21lIHRpc3N1ZXMsIGxpa2UgVGh5bXVzIGFuZCBIZWFydCBoYWQgb25seSBvbmUgY2hhbm5lbAogIGZvcihpIGluIDI6bnJvdyh0aXNzdWVfbWV0YWRhdGEpKXsKICAgIHN1YmZvbGRlciA9IHBhc3RlMCh0aXNzdWVfb2ZfaW50ZXJlc3QsICctJywgdGlzc3VlX21ldGFkYXRhJGNoYW5uZWxbaV0pCiAgICBuZXcuZGF0YSA8LSBSZWFkMTBYKGRhdGEuZGlyID0gaGVyZSgnMDBfZGF0YV9pbmdlc3QnLCAnMDFfZHJvcGxldF9yYXdfZGF0YScsICdkcm9wbGV0Jywgc3ViZm9sZGVyKSkKICAgIGNvbG5hbWVzKG5ldy5kYXRhKSA8LSBsYXBwbHkoY29sbmFtZXMobmV3LmRhdGEpLCBmdW5jdGlvbih4KSBwYXN0ZTAodGlzc3VlX21ldGFkYXRhJGNoYW5uZWxbaV0sICdfJywgeCkpCiAgICAKICAgIG5ldy5tZXRhZGF0YSA9IGRhdGEuZnJhbWUocm93Lm5hbWVzID0gY29sbmFtZXMobmV3LmRhdGEpKQogICAgbmV3Lm1ldGFkYXRhWydjaGFubmVsJ10gPSB0aXNzdWVfbWV0YWRhdGEkY2hhbm5lbFtpXQogICAgCiAgICByYXcuZGF0YSA9IGNiaW5kKHJhdy5kYXRhLCBuZXcuZGF0YSkKICAgIG1ldGEuZGF0YSA9IHJiaW5kKG1ldGEuZGF0YSwgbmV3Lm1ldGFkYXRhKQogIH0KfQoKcm5hbWVzID0gcm93Lm5hbWVzKG1ldGEuZGF0YSkKbWV0YS5kYXRhIDwtIG1lcmdlKG1ldGEuZGF0YSwgdGlzc3VlX21ldGFkYXRhLCBzb3J0ID0gRikKcm93Lm5hbWVzKG1ldGEuZGF0YSkgPC0gcm5hbWVzCmRpbShyYXcuZGF0YSkKY29ybmVyKHJhdy5kYXRhKQpoZWFkKG1ldGEuZGF0YSkKYGBgCgpPcmRlciB0aGUgY2VsbHMgYWxwaGFiZXRpY2FsbHkgdG8gZW5zdXJlIGNvbnNpc3RlbmN5LgoKYGBge3J9Cm9yZGVyZWRfY2VsbF9uYW1lcyA9IG9yZGVyKGNvbG5hbWVzKHJhdy5kYXRhKSkKcmF3LmRhdGEgPSByYXcuZGF0YVssb3JkZXJlZF9jZWxsX25hbWVzXQptZXRhLmRhdGEgPSBtZXRhLmRhdGFbb3JkZXJlZF9jZWxsX25hbWVzLF0KCmNvcm5lcihyYXcuZGF0YSkKaGVhZChtZXRhLmRhdGEpCmBgYAoKCgpQcm9jZXNzIHRoZSByYXcgZGF0YSBhbmQgbG9hZCBpdCBpbnRvIHRoZSBTZXVyYXQgb2JqZWN0LgoKYGBge3J9CiMgRmluZCBFUkNDJ3MsIGNvbXB1dGUgdGhlIHBlcmNlbnQgRVJDQywgYW5kIGRyb3AgdGhlbSBmcm9tIHRoZSByYXcgZGF0YS4KZXJjY3MgPC0gZ3JlcChwYXR0ZXJuID0gIl5FUkNDLSIsIHggPSByb3duYW1lcyh4ID0gcmF3LmRhdGEpLCB2YWx1ZSA9IFRSVUUpCnBlcmNlbnQuZXJjYyA8LSBNYXRyaXg6OmNvbFN1bXMocmF3LmRhdGFbZXJjY3MsIF0pL01hdHJpeDo6Y29sU3VtcyhyYXcuZGF0YSkKZXJjYy5pbmRleCA8LSBncmVwKHBhdHRlcm4gPSAiXkVSQ0MtIiwgeCA9IHJvd25hbWVzKHggPSByYXcuZGF0YSksIHZhbHVlID0gRkFMU0UpCnJhdy5kYXRhIDwtIHJhdy5kYXRhWy1lcmNjLmluZGV4LF0KCiMgQ3JlYXRlIHRoZSBTZXVyYXQgb2JqZWN0IHdpdGggYWxsIHRoZSBkYXRhCnRpc3MgPC0gQ3JlYXRlU2V1cmF0T2JqZWN0KHJhdy5kYXRhID0gcmF3LmRhdGEsIHByb2plY3QgPSB0aXNzdWVfb2ZfaW50ZXJlc3QsIAogICAgICAgICAgICAgICAgICAgIG1pbi5jZWxscyA9IDEsIG1pbi5nZW5lcyA9IDApCiMgQ29udGludWUgZnJvbSBoZXJlIG9ud2FyZHMgISAKdGlzcyA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSB0aXNzLCBtZXRhLmRhdGEpCnRpc3MgPC0gQWRkTWV0YURhdGEob2JqZWN0ID0gdGlzcywgcGVyY2VudC5lcmNjLCBjb2wubmFtZSA9ICJwZXJjZW50LmVyY2MiKQojIENoYW5nZSBkZWZhdWx0IG5hbWUgZm9yIHN1bXMgb2YgY291bnRzIGZyb20gblVNSSB0byBuUmVhZHMKIyBjb2xuYW1lcyh0aXNzQG1ldGEuZGF0YSlbY29sbmFtZXModGlzc0BtZXRhLmRhdGEpID09ICduVU1JJ10gPC0gJ25SZWFkcycKCiMgQ3JlYXRlIG1ldGFkYXRhIGNvbHVtbnMgZm9yIGNlbGxfb250b2xvZ3lfY2xhc3NzIGFuZCBzdWJjZWxsX29udG9sb2d5X2NsYXNzcwp0aXNzQG1ldGEuZGF0YVssJ2NlbGxfb250b2xvZ3lfY2xhc3MnXSA8LSBOQQp0aXNzQG1ldGEuZGF0YVssJ3N1YmNlbGxfb250b2xvZ3lfY2xhc3MnXSA8LSBOQQpgYGAKCgpDYWxjdWxhdGUgcGVyY2VudCByaWJvc29tYWwgZ2VuZXMuCgpgYGB7cn0Kcmliby5nZW5lcyA8LSBncmVwKHBhdHRlcm4gPSAiXlJwW3NsXVtbOmRpZ2l0Ol1dIiwgeCA9IHJvd25hbWVzKHggPSB0aXNzQGRhdGEpLCB2YWx1ZSA9IFRSVUUpCnBlcmNlbnQucmlibyA8LSBNYXRyaXg6OmNvbFN1bXModGlzc0ByYXcuZGF0YVtyaWJvLmdlbmVzLCBdKS9NYXRyaXg6OmNvbFN1bXModGlzc0ByYXcuZGF0YSkKdGlzcyA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSB0aXNzLCBtZXRhZGF0YSA9IHBlcmNlbnQucmlibywgY29sLm5hbWUgPSAicGVyY2VudC5yaWJvIikKYGBgCgpBIHNhbml0eSBjaGVjazogZ2VuZXMgcGVyIGNlbGwgdnMgcmVhZHMgcGVyIGNlbGwuCgpgYGB7cn0KR2VuZVBsb3Qob2JqZWN0ID0gdGlzcywgZ2VuZTEgPSAiblVNSSIsIGdlbmUyID0gIm5HZW5lIiwgdXNlLnJhdz1UKQpgYGAKCkZpbHRlciBvdXQgY2VsbHMgd2l0aCBmZXcgcmVhZHMgYW5kIGZldyBnZW5lcy4KCmBgYHtyfQp0aXNzIDwtIEZpbHRlckNlbGxzKG9iamVjdCA9IHRpc3MsIHN1YnNldC5uYW1lcyA9IGMoIm5HZW5lIiwgIm5VTUkiKSwgbG93LnRocmVzaG9sZHMgPSBjKDUwMCwgMTAwMCkpCmBgYAoKTm9ybWFsaXplIHRoZSBkYXRhLCB0aGVuIGNlbnRlciBhbmQgc2NhbGUuCgpgYGB7cn0KdGlzcyA8LSBOb3JtYWxpemVEYXRhKG9iamVjdCA9IHRpc3MsIHNjYWxlLmZhY3RvciA9IDFlNCkKdGlzcyA8LSBTY2FsZURhdGEob2JqZWN0ID0gdGlzcykKCnRpc3MgPC0gRmluZFZhcmlhYmxlR2VuZXMob2JqZWN0ID0gdGlzcywgZG8ucGxvdCA9IFRSVUUsIHguaGlnaC5jdXRvZmYgPSBJbmYsIHkuY3V0b2ZmID0gMC41KQpgYGAKCgpSdW4gUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcy4KYGBge3J9CnRpc3MgPC0gUnVuUENBKG9iamVjdCA9IHRpc3MsIGRvLnByaW50ID0gRkFMU0UpCnRpc3MgPC0gUHJvamVjdFBDQShvYmplY3QgPSB0aXNzLCBkby5wcmludCA9IEZBTFNFKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD04fQpQQ0hlYXRtYXAob2JqZWN0ID0gdGlzcywgcGMudXNlID0gMTozLCBjZWxscy51c2UgPSA1MDAsIGRvLmJhbGFuY2VkID0gVFJVRSwgbGFiZWwuY29sdW1ucyA9IEZBTFNFLCBudW0uZ2VuZXMgPSA4KQpgYGAKCkxhdGVyIG9uIChpbiBGaW5kQ2x1c3RlcnMgYW5kIFRTTkUpIHlvdSB3aWxsIHBpY2sgYSBudW1iZXIgb2YgcHJpbmNpcGFsIGNvbXBvbmVudHMgdG8gdXNlLiBUaGlzIGhhcyB0aGUgZWZmZWN0IG9mIGtlZXBpbmcgdGhlIG1ham9yIGRpcmVjdGlvbnMgb2YgdmFyaWF0aW9uIGluIHRoZSBkYXRhIGFuZCwgaWRlYWxseSwgc3VwcmVzc2luZyBub2lzZS4gVGhlcmUgaXMgbm8gY29ycmVjdCBhbnN3ZXIgdG8gdGhlIG51bWJlciB0byB1c2UsIGJ1dCBhIGRlY2VudCBydWxlIG9mIHRodW1iIGlzIHRvIGdvIHVudGlsIHRoZSBwbG90IHBsYXRlYXVzLgoKYGBge3J9ClBDRWxib3dQbG90KG9iamVjdCA9IHRpc3MpCmBgYAoKQ2hvb3NlIHRoZSBudW1iZXIgb2YgcHJpbmNpcGFsIGNvbXBvbmVudHMgdG8gdXNlLgpgYGB7cn0KIyBTZXQgbnVtYmVyIG9mIHByaW5jaXBhbCBjb21wb25lbnRzLiAKbi5wY3MgPSAxMApgYGAKClRoZSBjbHVzdGVyaW5nIGlzIHBlcmZvcm1lZCBiYXNlZCBvbiBhIG5lYXJlc3QgbmVpZ2hib3JzIGdyYXBoLiBDZWxscyB0aGF0IGhhdmUgc2ltaWxhciBleHByZXNzaW9uIHdpbGwgYmUgam9pbmVkIHRvZ2V0aGVyLiBUaGUgTG91dmFpbiBhbGdvcml0aG0gbG9va3MgZm9yIGdyb3VwcyBvZiBjZWxscyB3aXRoIGhpZ2ggbW9kdWxhcml0eS0tbW9yZSBjb25uZWN0aW9ucyB3aXRoaW4gdGhlIGdyb3VwIHRoYW4gYmV0d2VlbiBncm91cHMuIFRoZSByZXNvbHV0aW9uIHBhcmFtZXRlciBkZXRlcm1pbmVzIHRoZSBzY2FsZS4gSGlnaGVyIHJlc29sdXRpb24gd2lsbCBnaXZlIG1vcmUgY2x1c3RlcnMsIGxvd2VyIHJlc29sdXRpb24gd2lsbCBnaXZlIGZld2VyLgoKRm9yIHRoZSB0b3AtbGV2ZWwgY2x1c3RlcmluZywgYWltIHRvIHVuZGVyLWNsdXN0ZXIgaW5zdGVhZCBvZiBvdmVyLWNsdXN0ZXIuIEl0IHdpbGwgYmUgZWFzeSB0byBzdWJzZXQgZ3JvdXBzIGFuZCBmdXJ0aGVyIGFuYWx5emUgdGhlbSBiZWxvdy4KCmBgYHtyfQojIFNldCByZXNvbHV0aW9uIApyZXMudXNlZCA8LSAzLjUKCnRpc3MgPC0gRmluZENsdXN0ZXJzKG9iamVjdCA9IHRpc3MsIHJlZHVjdGlvbi50eXBlID0gInBjYSIsIGRpbXMudXNlID0gMTpuLnBjcywgCiAgICByZXNvbHV0aW9uID0gcmVzLnVzZWQsIHByaW50Lm91dHB1dCA9IDAsIHNhdmUuU05OID0gVFJVRSkKYGBgCgpXZSB1c2UgVFNORSBzb2xlbHkgdG8gdmlzdWFsaXplIHRoZSBkYXRhLgpgYGB7cn0KIyBJZiBjZWxscyBhcmUgdG9vIHNwcmVhZCBvdXQsIHlvdSBjYW4gcmFpc2UgdGhlIHBlcnBsZXhpdHkuIElmIHlvdSBoYXZlIGZldyBjZWxscywgdHJ5IGEgbG93ZXIgcGVycGxleGl0eSAoYnV0IG5ldmVyIGxlc3MgdGhhbiAxMCkuCnRpc3MgPC0gUnVuVFNORShvYmplY3QgPSB0aXNzLCBkaW1zLnVzZSA9IDE6bi5wY3MsIHNlZWQudXNlID0gMTAsIHBlcnBsZXhpdHk9MzApCmBgYAoKYGBge3J9ClRTTkVQbG90KG9iamVjdCA9IHRpc3MsIGRvLmxhYmVsID0gVCwgcHQuc2l6ZSA9IDEuMiwgbGFiZWwuc2l6ZSA9IDQpCmBgYAoKYGBge3J9ClRTTkVQbG90KHRpc3MsIGdyb3VwLmJ5PSJtb3VzZS5zZXgiKQpgYGAKCgpTaWduaWZpY2FudCBnZW5lczoKCmhlcGF0b2N5dGU6IEFsYiwgVHRyLCBBcG9hMSwgYW5kIFNlcnBpbmExYwpwZXJpY2VudHJhbDogQ3lwMmUxLCBHbHVsLCBPYXQsIEd1bG8KbWlkbG9idWxhcjogQXNzMSwgSGFtcCwgR3N0cDEsIFViYgpwZXJpcG9ydGFsOiBDeXAyZjIsIFBjazEsIEhhbCwgQ2RoMQoKZW5kb3RoZWxpYWwgY2VsbHM6IFBlY2FtMSwgTnJwMSwgS2RyKyBhbmQgT2l0MysKS3VwcGZlciBjZWxsczogRW1yMSwgQ2xlYzRmLCBDZDY4LCBJcmY3Ck5LL05LVCBjZWxsczogWmFwNzAsIElsMnJiLCBOa2c3LCBDeGNyNiwgS2xyMWMsIEd6bWEKQiBjZWxsczogQ2Q3OWEsIENkNzliLCBDZDc0IGFuZCBDZDE5CkltbXVuZSBjZWxsczogUHRwcmMKCmBgYHtyfQpnZW5lc19oZXAgPSBjKCdBbGInLCAnVHRyJywgJ0Fwb2ExJywgJ1NlcnBpbmExYycsIAogICAgICAgICAgICAgICAgICAgJ0N5cDJlMScsICdHbHVsJywgJ09hdCcsICdHdWxvJywKICAgICAgICAgICAgICAgICAgICdBc3MxJywgJ0hhbXAnLCAnR3N0cDEnLCAnVWJiJywKICAgICAgICAgICAgICAgICAgICdDeXAyZjInLCAnUGNrMScsICdIYWwnLCAnQ2RoMScpCmdlbmVzX2VuZG8gPSBjKCdQZWNhbTEnLCAnTnJwMScsICdLZHInLCdPaXQzJykKZ2VuZXNfa3VwcGZlciA9IGMoJ0VtcjEnLCAnQ2xlYzRmJywgJ0NkNjgnLCAnSXJmNycpCmdlbmVzX25rID0gYygnWmFwNzAnLCAnSWwycmInLCAnTmtnNycsICdDeGNyNicsICdHem1hJykKZ2VuZXNfYiA9IGMoJ0NkNzlhJywgJ0NkNzliJywgJ0NkNzQnKQpnZW5lc19iZWMgPSBjKCdFcGNhbScsICdLcnQxOScsICdLcnQ3JykKZ2VuZXNfaW1tdW5lID0gJ1B0cHJjJwoKYWxsX2dlbmVzID0gYyhnZW5lc19oZXAsIGdlbmVzX2VuZG8sIGdlbmVzX2t1cHBmZXIsIGdlbmVzX25rLCBnZW5lc19iLCBnZW5lc19iZWMsIGdlbmVzX2ltbXVuZSkKYGBgCgoKYGBge3IsIGVjaG89RkFMU0UsIGZpZy5oZWlnaHQ9MTYsIGZpZy53aWR0aD0xMn0KRmVhdHVyZVBsb3QodGlzcywgYyhnZW5lc19oZXApLCBwdC5zaXplID0gMSwgbkNvbCA9IDQsIGNvbHMudXNlID0gYygiZ3JleSIsICJyZWQiKSkKYGBgCgpEb3RwbG90cyBsZXQgeW91IHNlZSB0aGUgaW50ZW5zaXR5IG9mIGV4cHByZXNzaW9uIGFuZCB0aGUgZnJhY3Rpb24gb2YgY2VsbHMgZXhwcmVzc2luZyBmb3IgZWFjaCBvZiB5b3VyIGdlbmVzIG9mIGludGVyZXN0LgpUaGUgcmFkaXVzIHNob3dzIHlvdSB0aGUgcGVyY2VudCBvZiBjZWxscyBpbiB0aGF0IGNsdXN0ZXIgd2l0aCBhdCBsZWFzdCBvbmUgcmVhZCBzZXF1ZW5jZWQgZnJvbSB0aGF0IGdlbmUuIFRoZSBjb2xvciBsZXZlbCBpbmRpY2F0ZXMgdGhlIGF2ZXJhZ2UKWi1zY29yZSBvZiBnZW5lIGV4cHJlc3Npb24gZm9yIGNlbGxzIGluIHRoYXQgY2x1c3Rlciwgd2hlcmUgdGhlIHNjYWxpbmcgaXMgZG9uZSBvdmVyIHRha2VuIG92ZXIgYWxsIGNlbGxzIGluIHRoZSBzYW1wbGUuCgojV2UgaGF2ZSB2YXJpb3VzIGltbXVuZSBjZWxsIHR5cGVzIGluIHRoZSBsYXN0IGNsdXN0ZXIKYGBge3IsIGVjaG89RkFMU0UsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTEwfQpEb3RQbG90KHRpc3MsIGMoZ2VuZXNfa3VwcGZlciwgZ2VuZXNfbmssIGdlbmVzX2IsICJQdHByYyIpLCBwbG90LmxlZ2VuZCA9IFQsIGNvbC5tYXggPSAyLjUsIGRvLnJldHVybiA9IFQpICsgY29vcmRfZmxpcCgpCmBgYApgYGB7ciwgZWNobz1GQUxTRSwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTB9CkRvdFBsb3QodGlzcywgYWxsX2dlbmVzLCBwbG90LmxlZ2VuZCA9IFQsIGNvbC5tYXggPSAyLjUsIGRvLnJldHVybiA9IFQpICsgY29vcmRfZmxpcCgpCmBgYAoKVXNpbmcgdGhlIG1hcmtlcnMgYWJvdmUsIHdlIGNhbiBjb25maWRlbnRhbHkgbGFiZWwgbWFueSBvZiB0aGUgY2x1c3RlcnM6CgoxOTogZW5kb3RoZWxpYWwgY2VsbHMKMjA6IGJpbGUgZHVjdCBlcGl0aGVsaWFsIGNlbGxzCjIxOiBpbW11bmUgY2VsbHMKcmVzdCBhcmUgaGVwYXRvY3l0ZXMKCldlIHdpbGwgYWRkIHRob3NlIGNlbGxfb250b2xvZ3lfY2xhc3NzIHRvIHRoZSBkYXRhc2V0LgoKYGBge3J9CnRpc3MgPC0gU3Rhc2hJZGVudChvYmplY3QgPSB0aXNzLCBzYXZlLm5hbWUgPSAiY2x1c3Rlci5pZHMiKQoKY2x1c3Rlci5pZHMgPC0gYygwLDEsMiwzLDQsNSw2LDcsOCw5LDEwLDExLDEyLDEzLDE0LDE1LDE2LDE3LDE4LDE5LDIwLDIxKQoKZnJlZV9jZWxsX29udG9sb2d5X2NsYXNzIDwtIGMoCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJlbmRvdGhlbGlhbCBjZWxscyIsCiAgImJpbGUgZHVjdCBlcGl0aGVsaWFsIGNlbGxzIiwKICAiaW1tdW5lIGNlbGxzIikgCiAgCmNlbGxfb250b2xvZ3lfY2xhc3MgPC0gYygKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgImhlcGF0b2N5dGUiLAogICJoZXBhdG9jeXRlIiwKICAiaGVwYXRvY3l0ZSIsCiAgICJlbmRvdGhlbGlhbCBjZWxsIG9mIGhlcGF0aWMgc2ludXNvaWQiLCAKICAgImR1Y3QgZXBpdGhlbGlhbCBjZWxsIiwgCiAgICJsZXVrb2N5dGUiKQoKdmFsaWRhdGVfY2VsbF9vbnRvbG9neShjZWxsX29udG9sb2d5X2NsYXNzKQpjZWxsX29udG9sb2d5X2lkID0gY29udmVydF90b19jZWxsX29udG9sb2d5X2lkKGNlbGxfb250b2xvZ3lfY2xhc3MpCgp0aXNzQG1ldGEuZGF0YVsnZnJlZV9jZWxsX29udG9sb2d5X2NsYXNzJ10gPC0gYXMuY2hhcmFjdGVyKHBseXI6Om1hcHZhbHVlcyh4ID0gdGlzc0BpZGVudCwgZnJvbSA9IGNsdXN0ZXIuaWRzLCB0byA9IGZyZWVfY2VsbF9vbnRvbG9neV9jbGFzcykpCgp2YWxpZGF0ZV9jZWxsX29udG9sb2d5KGNlbGxfb250b2xvZ3lfY2xhc3MpCmNlbGxfb250b2xvZ3lfaWQgPSBjb252ZXJ0X3RvX2NlbGxfb250b2xvZ3lfaWQoY2VsbF9vbnRvbG9neV9jbGFzcykKCnRpc3NAbWV0YS5kYXRhWydmcmVlX2Fubm90YXRpb24nXSA8LSBhcy5jaGFyYWN0ZXIocGx5cjo6bWFwdmFsdWVzKHggPSB0aXNzQGlkZW50LCBmcm9tID0gY2x1c3Rlci5pZHMsIHRvID0gZnJlZV9hbm5vdGF0aW9uKSkKdGlzc0BtZXRhLmRhdGFbJ2NlbGxfb250b2xvZ3lfaWQnXSA8LSBhcy5jaGFyYWN0ZXIocGx5cjo6bWFwdmFsdWVzKHggPSB0aXNzQGlkZW50LCBmcm9tID0gY2x1c3Rlci5pZHMsIHRvID0gY2VsbF9vbnRvbG9neV9pZCkpCmBgYAoKIyMgQ2hlY2tpbmcgZm9yIGJhdGNoIGVmZmVjdHMKCkNvbG9yIGJ5IG1ldGFkYXRhLCBsaWtlIHBsYXRlIGJhcmNvZGUsIHRvIGNoZWNrIGZvciBiYXRjaCBlZmZlY3RzLgpgYGB7cn0KVFNORVBsb3Qob2JqZWN0ID0gc3VidGlzcywgZG8ucmV0dXJuID0gVFJVRSwgZ3JvdXAuYnkgPSAiY2hhbm5lbCIpCmBgYAoKIyMgU3ViY2x1c3RlcgoKYGBge3J9CnN1YnRpc3MgPSBTdWJzZXREYXRhKHRpc3MsIGlkZW50LnVzZSA9IGMoMCwxLDIsMyw0LDUsNiw3LDgsOSwxMCwxMSwxMiwxMywxNCwxNSwxNiwxNywxOCkpCmBgYApgYGB7cn0KVmxuUGxvdChvYmplY3QgPSBzdWJ0aXNzLCBmZWF0dXJlcy5wbG90ID0gYygibkdlbmUiLCAiblVNSSIpLCBuQ29sID0gMikKYGBgCmBgYHtyfQpzdWJ0aXNzIDwtIHN1YnRpc3MgJT4lIFNjYWxlRGF0YSgpICU+JSAKICBGaW5kVmFyaWFibGVHZW5lcyhkby5wbG90ID0gVFJVRSwgeC5oaWdoLmN1dG9mZiA9IEluZiwgeS5jdXRvZmYgPSAwLjUpICU+JQogIFJ1blBDQShkby5wcmludCA9IEZBTFNFKQpgYGAKCmBgYHtyfQpQQ0hlYXRtYXAob2JqZWN0ID0gc3VidGlzcywgcGMudXNlID0gMTozLCBjZWxscy51c2UgPSAyMCwgZG8uYmFsYW5jZWQgPSBUUlVFLCBsYWJlbC5jb2x1bW5zID0gRkFMU0UsIG51bS5nZW5lcyA9IDgpClBDRWxib3dQbG90KHN1YnRpc3MpCmBgYAoKCmBgYHtyfQpzdWIubi5wY3MgPSA4CnN1Yi5yZXMudXNlID0gLjUKc3VidGlzcyA8LSBzdWJ0aXNzICU+JSBGaW5kQ2x1c3RlcnMocmVkdWN0aW9uLnR5cGUgPSAicGNhIiwgZGltcy51c2UgPSAxOnN1Yi5uLnBjcywgCiAgICByZXNvbHV0aW9uID0gc3ViLnJlcy51c2UsIHByaW50Lm91dHB1dCA9IDAsIHNhdmUuU05OID0gVFJVRSkgJT4lCiAgCiAgICBSdW5UU05FKGRpbXMudXNlID0gMTpzdWIubi5wY3MsIHNlZWQudXNlID0gMTAsIHBlcnBsZXhpdHk9OCkKClRTTkVQbG90KG9iamVjdCA9IHN1YnRpc3MsIGRvLmxhYmVsID0gVCwgcHQuc2l6ZSA9IC41LCBsYWJlbC5zaXplID0gNCkKCmBgYAoKYGBge3IsIGVjaG89RkFMU0UsIGZpZy5oZWlnaHQ9MjUsIGZpZy53aWR0aD0yNX0KRmVhdHVyZVBsb3Qoc3VidGlzcywgZ2VuZXNfaGVwLGNvbHMudXNlID0gYygiZ3JleSIsICJyZWQiKSwgcHQuc2l6ZSA9IDQsIG5Db2wgPSA0KQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMH0KRG90UGxvdChzdWJ0aXNzLCBhbGxfZ2VuZXMsIGNvbC5tYXggPSAyLjUsIHBsb3QubGVnZW5kID0gVCwgZG8ucmV0dXJuID0gVCkgKyBjb29yZF9mbGlwKCkKYGBgCgpgYGB7cn0KQnVpbGRDbHVzdGVyVHJlZShzdWJ0aXNzKSAKYGBgCmBgYHtyLCBlY2hvPUZBTFNFLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9OH0KI2ZlbWFsZSBnZW5lcyBoYXZlIGxvd2VyIGV4cHJlc3Npb24gaW4gY2x1c3RlciA2IHJlbGF0aXZlIHRvIG90aGVyIGZlbWFsZSBjbHVzdGVycywgZXNwZWNhbGx5IFhpc3QKRmVhdHVyZVBsb3Qoc3VidGlzcyxjKCdNdXAyMCcsICdNdXAxJywnTXVwMTInLCAnTXVwMjEnLCAnQ3lwMmQ5JywgJ1hpc3QnLCAnQTFiZycsICdDeXAyYzY5JyksY29scy51c2UgPSBjKCJncmV5IiwgInJlZCIpLCBwdC5zaXplID0gMywgbkNvbCA9IDIpCmBgYAoKCkZyb20gdGhlc2UgZ2VuZXMsIGl0IGFwcGVhcnMgdGhhdCB0aGUgY2x1c3RlcnMgcmVwcmVzZW50OgoKMDogbWlkbG9idWxhciBtYWxlCjE6IHBlcmljZW50cmFsIGZlbWFsZQoyOiBwZXJpcG9ydGFsIGZlbWFsZQozOiBwZXJpcG9ydGFsIG1hbGUKNDogbWlkbG9idWxhciBtYWxlCjU6IHBlcmljZW50cmFsIG1hbGUKNjogbWlkbG9idWxhciBmZW1hbGUKNzogbWlkbG9idWxhciBmZW1hbGUKClRoZSBtdWx0aXR1ZGUgb2YgY2x1c3RlcnMgb2YgZWFjaCB0eXBlIGNvcnJlc3BvbmQgbW9zdGx5IHRvIGluZGl2aWR1YWwgYW5pbWFscy9zZXhlcy4KCmBgYHtyfQp0YWJsZShGZXRjaERhdGEoc3VidGlzcywgYygnbW91c2UuaWQnLCdpZGVudCcpKSAlPiUgZHJvcGxldmVscygpKQpgYGAKCmBgYHtyfQpzdWIuY2x1c3Rlci5pZHMgPC0gYygwLCAxLCAyLCAzLCA0LCA1LCA2LCA3KQpzdWIuZnJlZV9jZWxsX29udG9sb2d5X2NsYXNzIDwtIGMoIm1pZGxvYnVsYXIgbWFsZSIsICJwZXJpY2VudHJhbCBmZW1hbGUiLCAicGVyaXBvcnRhbCBmZW1hbGUiLCAicGVyaXBvcnRhbCBtYWxlIiwgIm1pZGxvYnVsYXIgbWFsZSIsICJwZXJpY2VudHJhbCBtYWxlIiwgIm1pZGxvYnVsYXIgZmVtYWxlIiwgIm1pZGxvYnVsYXIgZmVtYWxlIikKc3ViLmNlbGxfb250b2xvZ3lfY2xhc3MgPC0gYygiaGVwYXRvY3l0ZSIsICJoZXBhdG9jeXRlIiwgImhlcGF0b2N5dGUiLCAiaGVwYXRvY3l0ZSIsICJoZXBhdG9jeXRlIiwgImhlcGF0b2N5dGUiLCAiaGVwYXRvY3l0ZSIsICJoZXBhdG9jeXRlIikKCnZhbGlkYXRlX2NlbGxfb250b2xvZ3koc3ViLmNlbGxfb250b2xvZ3lfY2xhc3MpCnN1Yi5jZWxsX29udG9sb2d5X2lkID0gY29udmVydF90b19jZWxsX29udG9sb2d5X2lkKHN1Yi5jZWxsX29udG9sb2d5X2NsYXNzKQoKc3VidGlzc0BtZXRhLmRhdGFbJ2ZyZWVfY2VsbF9vbnRvbG9neV9jbGFzcyddIDwtIGFzLmNoYXJhY3RlcihwbHlyOjptYXB2YWx1ZXMoeCA9IHN1YnRpc3NAaWRlbnQsIGZyb20gPSBzdWIuY2x1c3Rlci5pZHMsIHRvID0gc3ViLmZyZWVfY2VsbF9vbnRvbG9neV9jbGFzcykpCnZhbGlkYXRlX2NlbGxfb250b2xvZ3koc3ViLmNlbGxfb250b2xvZ3lfY2xhc3MpCnN1Yi5jZWxsX29udG9sb2d5X2lkID0gY29udmVydF90b19jZWxsX29udG9sb2d5X2lkKHN1Yi5jZWxsX29udG9sb2d5X2NsYXNzKQoKc3VidGlzc0BtZXRhLmRhdGFbJ2ZyZWVfYW5ub3RhdGlvbiddIDwtIGFzLmNoYXJhY3RlcihwbHlyOjptYXB2YWx1ZXMoeCA9IHN1YnRpc3NAaWRlbnQsIGZyb20gPSBzdWIuY2x1c3Rlci5pZHMsIHRvID0gc3ViLmZyZWVfYW5ub3RhdGlvbikpCnN1YnRpc3NAbWV0YS5kYXRhWydjZWxsX29udG9sb2d5X2NsYXNzJ10gPC0gYXMuY2hhcmFjdGVyKHBseXI6Om1hcHZhbHVlcyh4ID0gc3VidGlzc0BpZGVudCwgZnJvbSA9IHN1Yi5jbHVzdGVyLmlkcywgdG8gPSBzdWIuY2VsbF9vbnRvbG9neV9jbGFzcykpCnN1YnRpc3NAbWV0YS5kYXRhWydjZWxsX29udG9sb2d5X2lkJ10gPC0gYXMuY2hhcmFjdGVyKHBseXI6Om1hcHZhbHVlcyh4ID0gc3VidGlzc0BpZGVudCwgZnJvbSA9IHN1Yi5jbHVzdGVyLmlkcywgdG8gPSBzdWIuY2VsbF9vbnRvbG9neV9pZCkpCmBgYAoKIyMgQWRkIHN1YmNsdXN0ZXIgY2VsbF9vbnRvbG9neV9jbGFzc3MgdG8gbWFpbiBjZWxsX29udG9sb2d5X2NsYXNzCgpgYGB7cn0Kc3ViLmNlbGxzID0gcm93bmFtZXMoc3VidGlzc0BtZXRhLmRhdGEpCgp0aXNzQG1ldGEuZGF0YVtzdWIuY2VsbHMsICdmcmVlX2NlbGxfb250b2xvZ3lfY2xhc3MnXSA9IHN1YnRpc3NAbWV0YS5kYXRhWywnZnJlZV9jZWxsX29udG9sb2d5X2NsYXNzJ10KdGlzc0BtZXRhLmRhdGFbc3ViLmNlbGxzLCAnY2VsbF9vbnRvbG9neV9jbGFzcyddID0gc3VidGlzc0BtZXRhLmRhdGFbLCdjZWxsX29udG9sb2d5X2NsYXNzJ10KdGlzc0BtZXRhLmRhdGFbc3ViLmNlbGxzLCAnY2VsbF9vbnRvbG9neV9pZCddID0gc3VidGlzc0BtZXRhLmRhdGFbLCdjZWxsX29udG9sb2d5X2lkJ10KYGBgCgoKIyMgQ2hlY2tpbmcgZm9yIGJhdGNoIGVmZmVjdHMKCkNvbG9yIGJ5IG1ldGFkYXRhLCBsaWtlIHBsYXRlIGJhcmNvZGUsIHRvIGNoZWNrIGZvciBiYXRjaCBlZmZlY3RzLgpgYGB7cn0KVFNORVBsb3Qob2JqZWN0ID0gc3VidGlzcywgZG8ucmV0dXJuID0gVFJVRSwgZ3JvdXAuYnkgPSAibW91c2Uuc2V4IikKYGBgCgojIEZpbmFsIGNvbG9yaW5nCgpDb2xvciBieSBjZWxsIG9udG9sb2d5IGNsYXNzIG9uIHRoZSBvcmlnaW5hbCBUU05FLgoKYGBge3J9ClRTTkVQbG90KG9iamVjdCA9IHRpc3MsIGRvLnJldHVybiA9IFRSVUUsIGdyb3VwLmJ5ID0gImNlbGxfb250b2xvZ3lfY2xhc3MiKQpgYGAKCiMgU2F2ZSB0aGUgUm9iamVjdCBmb3IgbGF0ZXIKCmBgYHtyfQpmaWxlbmFtZSA9IGhlcmUoJzAwX2RhdGFfaW5nZXN0JywgJzA0X3Rpc3N1ZV9yb2JqX2dlbmVyYXRlZCcsIAogICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoImRyb3BsZXQiLCB0aXNzdWVfb2ZfaW50ZXJlc3QsICJfc2V1cmF0X3N1YnRpc3NfLjVyZXMuUm9iaiIpKQpwcmludChmaWxlbmFtZSkKc2F2ZShzdWJ0aXNzLCBmaWxlPWZpbGVuYW1lKQpgYGAKCmBgYHtyfQojIFRvIHJlbG9hZCBhIHNhdmVkIG9iamVjdApmaWxlbmFtZSA9IGhlcmUoJzAwX2RhdGFfaW5nZXN0JywgJzA0X3Rpc3N1ZV9yb2JqX2dlbmVyYXRlZCcsIAogICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKCJkcm9wbGV0IiwgdGlzc3VlX29mX2ludGVyZXN0LCAiX3NldXJhdF90aXNzLlJvYmoiKSkKbG9hZChmaWxlPWZpbGVuYW1lKQpgYGAKCgojIEV4cG9ydCB0aGUgZmluYWwgbWV0YWRhdGEKClNvIHRoYXQgQmlvaHViIGNhbiBlYXNpbHkgY29tYmluZSBhbGwgeW91ciBjZWxsX29udG9sb2d5X2NsYXNzcywgcGxlYXNlIGV4cG9ydCB0aGVtIGFzIGEgc2ltcGxlIGNzdi4KCmBgYHtyfQpoZWFkKHRpc3NAbWV0YS5kYXRhKQpgYGAKCmBgYHtyfQpmaWxlbmFtZSA9IGhlcmUoJzAwX2RhdGFfaW5nZXN0JywgJzAzX3Rpc3N1ZV9jZWxsX29udG9sb2d5X2NsYXNzX2NzdicsIAogICAgICAgICAgICAgICAgICAgICBwYXN0ZTAodGlzc3VlX29mX2ludGVyZXN0LCAiX2NlbGxfb250b2xvZ3lfY2xhc3MuY3N2IikpCndyaXRlLmNzdihGZXRjaERhdGEodGlzcywgYygncGxhdGUuYmFyY29kZScsJ2NlbGxfb250b2xvZ3lfY2xhc3MnLCdjZWxsX29udG9sb2d5X2lkJywgJ2ZyZWVfY2VsbF9vbnRvbG9neV9jbGFzcycsICd0U05FXzEnLCAndFNORV8yJykpLCBmaWxlPWZpbGVuYW1lKQpgYGAKCgo=